// Copyright 2004-present Facebook. All Rights Reserved.

#include "fboss/agent/hw/bcm/BcmWarmBootState.h"

#include "fboss/agent/Constants.h"
#include "fboss/agent/hw/bcm/BcmEgress.h"
#include "fboss/agent/hw/bcm/BcmHost.h"
#include "fboss/agent/hw/bcm/BcmIntf.h"
#include "fboss/agent/hw/bcm/BcmLabeledEgress.h"
#include "fboss/agent/hw/bcm/BcmLabeledTunnel.h"
#include "fboss/agent/hw/bcm/BcmLabeledTunnelEgress.h"
#include "fboss/agent/hw/bcm/BcmMultiPathNextHop.h"
#include "fboss/agent/hw/bcm/BcmQosPolicyTable.h"
#include "fboss/agent/hw/bcm/BcmRoute.h"
#include "fboss/agent/hw/bcm/BcmSwitch.h"
#include "fboss/agent/state/Interface.h"
#include "fboss/agent/state/RouteNextHopEntry.h"

namespace facebook::fboss {

template <>
folly::dynamic BcmWarmBootState::toFollyDynamic(
    const BcmHostKey& key,
    const std::shared_ptr<BcmHostIf>& host) const;

template <>
folly::dynamic BcmWarmBootState::toFollyDynamic(
    const BcmMultiPathNextHopKey& key,
    const std::shared_ptr<BcmMultiPathNextHop>& multiPathNextHop) const;

template <>
folly::dynamic BcmWarmBootState::toFollyDynamic(
    const BcmLabeledHostKey& key,
    const std::shared_ptr<BcmMplsNextHop>& host) const;

template <>
folly::dynamic BcmWarmBootState::egressToFollyDynamic(
    const BcmEgress* egress) const;

template <>
folly::dynamic BcmWarmBootState::egressToFollyDynamic(
    const BcmEcmpEgress* egress) const;

template <>
folly::dynamic BcmWarmBootState::egressToFollyDynamic(
    const BcmLabeledEgress* egress) const;

template <>
folly::dynamic BcmWarmBootState::egressToFollyDynamic(
    const BcmLabeledTunnelEgress* egress) const;

folly::dynamic BcmWarmBootState::hostTableToFollyDynamic() const {
  folly::dynamic hostsJson = folly::dynamic::array;
  for (const auto& hostTableEntry : *hw_->getHostTable()) {
    std::shared_ptr<BcmHostIf> host = hostTableEntry.second.lock();
    hostsJson.push_back(toFollyDynamic(hostTableEntry.first, host));
  }

  // host entries are programmed to route table if host table is
  // not available in hardware
  for (const auto& hostRouteEntry : hw_->routeTable()->getHostRoutes()) {
    std::shared_ptr<BcmHostIf> host = hostRouteEntry.second.lock();
    hostsJson.push_back(toFollyDynamic(hostRouteEntry.first, host));
  }

  // previously, ECMP next hops were maintained as a part of BcmHostTable, even
  // though, they did not really go in BCM ASIC's host table. this was reflected
  // in warmboot state file generated by FBOSS for BCMSwitch. Retaining backward
  // compatibility, by doing "multi-Path nexthop" toFollyDynamic here.
  folly::dynamic ecmpHostsJson = folly::dynamic::array;
  auto& ecmpHosts = hw_->getMultiPathNextHopTable()->getNextHops();
  for (const auto& vrfNhopsAndHost : ecmpHosts) {
    auto ecmpHost = vrfNhopsAndHost.second.lock();
    ecmpHostsJson.push_back(toFollyDynamic(vrfNhopsAndHost.first, ecmpHost));
  }

  folly::dynamic hostTable = folly::dynamic::object;
  hostTable[kHosts] = std::move(hostsJson);
  hostTable[kEcmpHosts] = std::move(ecmpHostsJson);
  return hostTable;
}

template <>
folly::dynamic BcmWarmBootState::toFollyDynamic(
    const BcmHostKey& hostKey,
    const std::shared_ptr<BcmHostIf>& bcmHost) const {
  folly::dynamic host = folly::dynamic::object;
  host[kVrf] = hostKey.getVrf();
  host[kIp] = hostKey.addr().str();
  if (hostKey.intfID().has_value()) {
    host[kIntf] = static_cast<uint32_t>(hostKey.intfID().value());
  }
  host[kPort] = 0;
  auto egressPort = bcmHost->getEgressPortDescriptor();
  if (egressPort) {
    host[kPort] = egressPort->toFollyDynamic();
  }
  host[kEgressId] = bcmHost->getEgressId();
  auto* egress = bcmHost->getEgress();
  if (egress) {
    // owned egress, BcmHost entry is not host route entry.
    host[kEgress] = egressToFollyDynamic(egress);
  }
  host[kClassID] = bcmHost->getLookupClassId();
  return host;
}

template <>
folly::dynamic BcmWarmBootState::toFollyDynamic(
    const BcmMultiPathNextHopKey& key,
    const std::shared_ptr<BcmMultiPathNextHop>& multiPathNextHop) const {
  folly::dynamic ecmpHost = folly::dynamic::object;
  ecmpHost[kVrf] = key.first;
  folly::dynamic nhops = folly::dynamic::array;
  for (const auto& nhop : key.second) {
    nhops.push_back(nhop.toFollyDynamic());
  }
  ecmpHost[kNextHops] = std::move(nhops);
  ecmpHost[kEgressId] = multiPathNextHop->getEgressId();
  ecmpHost[kEcmpEgressId] = multiPathNextHop->getEcmpEgressId();
  auto ecmpEgress = multiPathNextHop->getEgress();
  if (ecmpEgress) {
    ecmpHost[kEcmpEgress] = egressToFollyDynamic(ecmpEgress);
  }
  return ecmpHost;
}

template <>
folly::dynamic BcmWarmBootState::egressToFollyDynamic(
    const BcmEgress* egress) const {
  CHECK(egress);
  folly::dynamic egressDynamic = folly::dynamic::object;
  egressDynamic[kEgressId] = egress->getID();
  egressDynamic[kMac] = egress->getMac().toString();
  egressDynamic[kIntfId] = egress->getIntfId();
  return egressDynamic;
}

template <>
folly::dynamic BcmWarmBootState::egressToFollyDynamic(
    const BcmEcmpEgress* ecmpEgress) const {
  CHECK(ecmpEgress);
  folly::dynamic ecmpEgressDynamic = folly::dynamic::object;
  ecmpEgressDynamic[kEgressId] = ecmpEgress->getID();
  folly::dynamic paths = folly::dynamic::array;
  for (const auto& path : ecmpEgress->egressId2Weight()) {
    for (int i = 0; i < path.second; i++) {
      paths.push_back(path.first);
    }
  }
  ecmpEgressDynamic[kPaths] = std::move(paths);
  return ecmpEgressDynamic;
}

template <>
folly::dynamic BcmWarmBootState::egressToFollyDynamic(
    const BcmLabeledEgress* egress) const {
  auto egressDynamic =
      egressToFollyDynamic(static_cast<const BcmEgress*>(egress));
  egressDynamic[kLabel] = egress->getLabel();
  return egressDynamic;
}

template <>
folly::dynamic BcmWarmBootState::egressToFollyDynamic(
    const BcmLabeledTunnelEgress* egress) const {
  auto egressDynamic =
      egressToFollyDynamic(static_cast<const BcmLabeledEgress*>(egress));
  egressDynamic[kMplsTunnel] = mplsTunnelToFollyDynamic(egress->getTunnel());
  return egressDynamic;
}

folly::dynamic BcmWarmBootState::mplsTunnelToFollyDynamic(
    BcmLabeledTunnel* tunnel) const {
  folly::dynamic tunnelDynamic = folly::dynamic::object;
  tunnelDynamic[kIntf] = tunnel->getTunnelInterface();
  folly::dynamic labels = folly::dynamic::array;
  for (const auto& label : tunnel->getTunnelStack()) {
    labels.push_back(label);
  }
  tunnelDynamic[kStack] = std::move(labels);
  return tunnelDynamic;
}

folly::dynamic BcmWarmBootState::mplsNextHopsToFollyDynamic() const {
  folly::dynamic mplsNextHopsJson = folly::dynamic::array;
  for (const auto& mplsNextHopTableEntry :
       hw_->getMplsNextHopTable()->getNextHops()) {
    auto mplsNextHop = mplsNextHopTableEntry.second.lock();
    mplsNextHopsJson.push_back(
        toFollyDynamic(mplsNextHopTableEntry.first, mplsNextHop));
  }
  return mplsNextHopsJson;
}

template <>
folly::dynamic BcmWarmBootState::toFollyDynamic(
    const BcmLabeledHostKey& key,
    const std::shared_ptr<BcmMplsNextHop>& nexthop) const {
  folly::dynamic mplsNextHopDynamic = folly::dynamic::object;

  mplsNextHopDynamic[kVrf] = key.getVrf();
  mplsNextHopDynamic[kIp] = key.addr().str();
  if (key.intfID().has_value()) {
    mplsNextHopDynamic[kIntf] = static_cast<uint32_t>(key.intfID().value());
  }

  mplsNextHopDynamic[kEgressId] = nexthop->getEgressId();
  if (!key.needsMplsTunnel()) {
    auto* egress = nexthop->getBcmLabeledEgress();
    if (egress) {
      mplsNextHopDynamic[kEgress] = egressToFollyDynamic(egress);
    }
    mplsNextHopDynamic[kLabel] = key.getLabel();
  } else {
    auto* egress = nexthop->getBcmLabeledTunnelEgress();
    if (egress) {
      mplsNextHopDynamic[kEgress] = egressToFollyDynamic(egress);
    }
    folly::dynamic labels = folly::dynamic::array;
    for (const auto& label : key.tunnelLabelStack()) {
      labels.push_back(label);
    }
    mplsNextHopDynamic[kStack] = std::move(labels);
  }

  return mplsNextHopDynamic;
}

folly::dynamic BcmWarmBootState::intfTableToFollyDynamic() const {
  folly::dynamic intfTableDynamic = folly::dynamic::array;

  for (const auto& intf : hw_->getIntfTable()->getInterfaces()) {
    folly::dynamic intfDynamic = folly::dynamic::object;
    intfDynamic[kIntfId] = intf.second->getBcmIfId();
    intfDynamic[kVlan] =
        static_cast<uint16_t>(intf.second->getInterface()->getVlanID());
    intfTableDynamic.push_back(intfDynamic);
  }
  return intfTableDynamic;
}

folly::dynamic BcmWarmBootState::qosTableToFollyDynamic() const {
  folly::dynamic qosTableDynamic = folly::dynamic::object;

  const auto& qosPolicyMap = hw_->getQosPolicyTable()->getQosPolicyMap();
  for (const auto& nameToQosPolicy : qosPolicyMap) {
    folly::dynamic qosPolicy = folly::dynamic::object;
    const auto& name = nameToQosPolicy.first;
    const auto policy = nameToQosPolicy.second.get();
    if (auto ingressDscpMap = policy->getIngressDscpQosMap()) {
      qosPolicy[kInDscp] = ingressDscpMap->getHandle();
    }
    if (auto ingressExpMap = policy->getIngressExpQosMap()) {
      qosPolicy[kInExp] = ingressExpMap->getHandle();
    }
    if (auto egressExpMap = policy->getEgressExpQosMap()) {
      qosPolicy[kOutExp] = egressExpMap->getHandle();
    }
    qosTableDynamic[name] = qosPolicy;
  }
  return qosTableDynamic;
}

} // namespace facebook::fboss
